/*******************************************************************************
 * Copyright (c) 2002 Boris Pruessmann and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.ui.dialogs;

import com.google.common.collect.Lists;

import net.sourceforge.perforce.ui.PerforceUIPlugin;
import net.sourceforge.perforce.ui.internal.Policy;

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.PlatformUI;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
abstract class AbstractChangeSpecDialog extends Dialog {

  /** Constant: Width Hint for Files Table */
  private static final int WIDTH_HINT = 450;

  /** Constant: Height Hint for Files Table */
  private static final int HEIGHT_HINT = 100;

  /** Text Field: description text */
  private Text descriptionText;

  /** Table Viewer: Files */
  private TableViewer fileViewer;

  /** Table Viewer: Files */
  private TableViewer jobViewer;

  /** Stores the background of the status area. */
  private Color statusBackground;

  /** Stores the bold font */
  private Font statusFont;

  /** Stores the images (for disposing) */
  private List<Image> images = new LinkedList<Image>();

  /** Holds the dialogs position. */
  private Rectangle dialogPosition;

  private String[] jobs = new String[0];

  /**
   * Constructor for AbstractChangeSpecDialog.
   * 
   * @param shell the parent shell.
   */
  public AbstractChangeSpecDialog(Shell shell) {
    super(shell);
    setShellStyle(SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE);
  }

  @Override
  public void create() {
    jobs = getJobs();
    super.create();

    readSettings();

    Shell shell = getShell();
    if (dialogPosition != null) {
      shell.setBounds(dialogPosition);
    }
  }

  @Override
  public boolean close() {
    storeSettings();
    boolean result = super.close();

    statusBackground.dispose();
    statusFont.dispose();

    for (Iterator<Image> it = images.iterator(); it.hasNext();) {
      Image image = it.next();
      image.dispose();
    }

    return result;
  }


  @Override
  protected Control createDialogArea(Composite parent) {
    getShell().setText(getTitle());

    Composite composite = new Composite(parent, SWT.NULL);
    composite.setFont(parent.getFont());
    GridLayout layout = new GridLayout();
    layout.numColumns = 1;
    composite.setLayout(layout);
    composite.setLayoutData(new GridData(GridData.FILL_BOTH));

    createStatusArea(composite);

    // Description Area
    Label descriptionLabel = createLabel(composite, Policy.bind("ChangeSpecDialog.description")); //$NON-NLS-1$
    GridData data = new GridData(GridData.FILL_HORIZONTAL);
    descriptionLabel.setLayoutData(data);

    int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
    descriptionText = createDescriptionTextControl(composite, style);
    descriptionText.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        String description = descriptionText.getText();
        if (description.equals("<enter description here>")) {
          getButton(IDialogConstants.OK_ID).setEnabled(false);
        } else {
          getButton(IDialogConstants.OK_ID).setEnabled(true);
        }
      }
    });

    data = new GridData(GridData.FILL_BOTH);
    data.widthHint = WIDTH_HINT;
    data.heightHint = HEIGHT_HINT;
    descriptionText.setLayoutData(data);
    descriptionText.setText(getDescription());

    descriptionText.selectAll();
    descriptionText.setFocus();

    // Files Area
    String[] files = getFiles();

    fileViewer = buildCheckableTable(composite, "files", files);

    if (jobs.length > 0) jobViewer = buildCheckableTable(composite, "jobs", jobs);

    Label separator2 = new Label(composite, SWT.HORIZONTAL | SWT.SEPARATOR);
    data = new GridData(GridData.FILL_HORIZONTAL);
    separator2.setLayoutData(data);

    setFileIcons(files);
    return composite;
  }

  protected abstract String[] getJobs();

  private void setFileIcons(String[] files) {
    final HashMap<ImageDescriptor, Image> imageMap = new HashMap<ImageDescriptor, Image>();

    TableItem[] items = fileViewer.getTable().getItems();
    for (int i = 0, length = items.length; i < length; i++) {
      items[i].setChecked(true);

      String filename = files[i].substring(files[i].lastIndexOf('/') + 1);
      ImageDescriptor descriptor =
          PlatformUI.getWorkbench().getEditorRegistry().getImageDescriptor(filename);

      Image image = imageMap.get(descriptor);
      if (image == null) {
        image = descriptor.createImage();
        images.add(image);
        imageMap.put(descriptor, image);
      }

      items[i].setImage(image);
    }
  }

  private TableViewer buildCheckableTable(Composite composite, String labelKey, String[] elements) {
    GridData data;
    Label filesLabel = createLabel(composite, Policy.bind("ChangeSpecDialog." + labelKey)); //$NON-NLS-1$
    data = new GridData(GridData.FILL_HORIZONTAL);
    filesLabel.setLayoutData(data);
    TableViewer viewer = createTable(composite);

    viewer.add(elements);
    TableItem[] items = viewer.getTable().getItems();
    for (int i = 0, length = items.length; i < length; i++) {
      items[i].setChecked(true);
    }

    createTableButtonBar(composite, viewer);
    return viewer;
  }

  /**
   * Called to create the button areas placed under the files area.
   */
  abstract protected void createTableButtonBar(Composite composite, TableViewer viewer);

  /**
   * Called to retrieve the file viewer.
   * 
   * @return the file viewer.
   */
  protected TableViewer getFileViewer() {
    return fileViewer;
  }

  /**
   * Called to create the description text control.
   * 
   * @param parent the parent composite.
   * @param style the style.
   * @return the text control.
   */
  protected Text createDescriptionTextControl(Composite parent, int style) {
    Text result = new Text(parent, style);
    result.setFont(parent.getFont());

    return result;
  }

  /**
   * Called to create the files table control.
   * 
   * @param parent the parent composite.
   * @param style the style.
   * @return the table control.
   */
  protected Table createFileTableControl(Composite parent, int style) {
    Table result = new Table(parent, style);
    result.setFont(parent.getFont());

    return result;
  }

  /**
   * Returns the currently entered description text.
   * 
   * @return the description text.
   */
  String getDescriptionText() {
    return descriptionText.getText();
  }

  /**
   * Called to retrieve an array of files that are 'checked'
   * 
   * @return array of selected files.
   */
  String[] getSelectedFiles() {
    TableViewer viewer = fileViewer;
    return getSelectedItems(viewer);
  }

  String[] getSelectedJobs() {
    if (jobViewer == null) return new String[0];
    TableViewer viewer = jobViewer;
    return getSelectedItems(viewer);
  }

  private String[] getSelectedItems(TableViewer viewer) {
    List<String> result = Lists.newArrayList();

    TableItem[] items = viewer.getTable().getItems();
    for (int i = 0, length = items.length; i < length; i++) {
      if (items[i].getChecked()) {
        result.add(items[i].getText());
      }
    }

    return result.toArray(new String[result.size()]);
  }

  int getJobCount() {
    if (jobViewer == null) return 0;
    TableItem[] items = jobViewer.getTable().getItems();
    return items.length;
  }

  /**
   * Returns the file count.
   * 
   * @return number of files.
   */
  int getFileCount() {
    TableItem[] items = fileViewer.getTable().getItems();
    return items.length;
  }

  /**
   * Called to retrieve the description to display.
   * 
   * @return the description.
   */
  abstract String getDescription();

  /**
   * Called to retrieve the title of the dialog.
   * 
   * @return the dialog's title.
   */
  abstract String getTitle();

  /**
   * Called to retrieve the changelist from the specdata.
   * 
   * @return the changelist.
   */
  abstract String getChangelist();

  /**
   * Called to retrieve the date from the specdata.
   * 
   * @return the date.
   */
  abstract String getDate();

  /**
   * Called to retrieve the client name from the specdata.
   * 
   * @return the client name.
   */
  abstract String getClientName();

  /**
   * Called to retrieve the user name from the specdata.
   * 
   * @return the user name.
   */
  abstract String getUserName();

  /**
   * Called to retrieve the changelist status from the specdata.
   * 
   * @return the status.
   */
  abstract String getStatus();

  /**
   * Retrieves an array of files stored in the specdata.
   * 
   * @return array of file specs
   */
  abstract String[] getFiles();

  /**
   * Called to create the status area.
   * 
   * @param parent the parent composite.
   */
  private void createStatusArea(Composite parent) {
    StatusComposite composite = new StatusComposite(parent, SWT.NONE);
    composite.setFont(parent.getFont());
    GridLayout layout = new GridLayout();
    layout.numColumns = 5;
    composite.setLayout(layout);

    GridData data = new GridData(GridData.FILL_HORIZONTAL);
    composite.setLayoutData(data);

    statusBackground = new Color(composite.getDisplay(), new RGB(255, 255, 204));
    composite.setBackground(statusBackground);

    // Build Status Area
    Label l = createLabel(composite, Policy.bind("ChangeSpecDialog.change")); //$NON-NLS-1$
    l.setBackground(statusBackground);

    FontData fontData = l.getFont().getFontData()[0];
    fontData.setStyle(SWT.BOLD);
    statusFont = new Font(composite.getDisplay(), fontData);
    l.setFont(statusFont);
    l.pack();

    l = createLabel(composite, Policy.bind("ChangeSpecDialog.date")); //$NON-NLS-1$
    l.setBackground(statusBackground);
    l.setFont(statusFont);
    l.pack();

    l = createLabel(composite, Policy.bind("ChangeSpecDialog.client")); //$NON-NLS-1$
    l.setBackground(statusBackground);
    l.setFont(statusFont);
    l.pack();

    l = createLabel(composite, Policy.bind("ChangeSpecDialog.user")); //$NON-NLS-1$
    l.setBackground(statusBackground);
    l.setFont(statusFont);
    l.pack();

    l = createLabel(composite, Policy.bind("ChangeSpecDialog.status")); //$NON-NLS-1$
    l.setBackground(statusBackground);
    l.setFont(statusFont);
    l.pack();

    l = createLabel(composite, getChangelist());
    l.setBackground(statusBackground);

    l = createLabel(composite, getDate());
    l.setBackground(statusBackground);

    l = createLabel(composite, getClientName());
    l.setBackground(statusBackground);

    l = createLabel(composite, getUserName());
    l.setBackground(statusBackground);

    l = createLabel(composite, getStatus());
    l.setBackground(statusBackground);
  }

  /**
   * Called to create a text label with the given text.
   * 
   * @param parent the parent composite.
   * @param text the text.
   * @return the Label object.
   */
  private Label createLabel(Composite parent, String text) {
    Label result = new Label(parent, SWT.NULL);
    result.setFont(parent.getFont());
    result.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL));
    result.setText(text);

    return result;
  }

  /**
   * Called to create the File table.
   * 
   * @param parent the parent composite.
   */
  private TableViewer createTable(Composite parent) {
    int style = SWT.BORDER | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL;
    style |= SWT.CHECK;

    final Table table = createFileTableControl(parent, style);

    TableLayout layout = new TableLayout();
    layout.addColumnData(new ColumnWeightData(1));

    table.setLayout(layout);
    table.setLinesVisible(true);

    TableViewer viewer = new TableViewer(table);

    GridData data = new GridData(GridData.FILL_BOTH);
    data.widthHint = WIDTH_HINT;
    data.heightHint = HEIGHT_HINT;

    table.setLayoutData(data);

    // Add KeyListener to emulate P4Win's selection behavior
    table.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent event) {
        if (event.character == ' ') {
          int index = table.getSelectionIndex();
          TableItem item = table.getItem(index);

          boolean selection = item.getChecked();
          TableItem[] items = table.getSelection();
          for (int i = 0; i < items.length; i++) {
            items[i].setChecked(selection);
          }
        }
      }
    });
    return viewer;
  }

  /**
   * Returns the dialog's boundaries.
   * 
   * @return the dialog's boundaries
   */
  private Rectangle getDialogBoundaries() {
    if (okToUse(getShell())) {
      return getShell().getBounds();
    } else {
      return dialogPosition;
    }
  }

  /**
   * Returns <code>true</code> if control can be used
   * 
   * @param control the control to be checked
   * @return <code>true</code> if control can be used
   */
  private boolean okToUse(Control control) {
    return control != null && !control.isDisposed();
  }

  /**
   * Retrieves the associated IDialogSettings.
   * 
   * @return the dialog settings.
   */
  private IDialogSettings getDialogSettings() {
    String suffix = "";

    // hmmm, what's this magic string?
    if (jobs.length > 0) {
      suffix = "@w/jobs";
    }
    IDialogSettings pluginSettings = PerforceUIPlugin.getPlugin().getDialogSettings();
    IDialogSettings dialogSettings = pluginSettings.getSection(getClass().getName() + suffix);

    if (dialogSettings == null) {
      dialogSettings = pluginSettings.addNewSection(getClass().getName());
    }

    return dialogSettings;
  }

  /** Reads the dialogs settings. */
  private void readSettings() {
    IDialogSettings s = getDialogSettings();
    try {
      int x = s.getInt("x"); //$NON-NLS-1$
      int y = s.getInt("y"); //$NON-NLS-1$
      int width = s.getInt("width"); //$NON-NLS-1$
      int height = s.getInt("height"); //$NON-NLS-1$
      dialogPosition = new Rectangle(x, y, width, height);
    } catch (NumberFormatException e) {
      dialogPosition = null;
    }
  }

  /** Stores the dialogs settings. */
  private void storeSettings() {
    IDialogSettings s = getDialogSettings();

    Rectangle dialogPosition = getDialogBoundaries();
    s.put("x", dialogPosition.x); //$NON-NLS-1$
    s.put("y", dialogPosition.y); //$NON-NLS-1$
    s.put("width", dialogPosition.width); //$NON-NLS-1$
    s.put("height", dialogPosition.height); //$NON-NLS-1$
  }
}
